package com.vavr.checkincms.service;

import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.vavr.checkincms.common.constant.CommonConstants;
import com.vavr.checkincms.common.domain.ResultEnum;
import com.vavr.checkincms.common.exception.BaseException;
import com.vavr.checkincms.common.util.HmacSHAEncoderUtils;
import com.vavr.checkincms.configuration.CheckinProperties;
import com.vavr.checkincms.constant.CheckinConstants;
import com.vavr.checkincms.domain.CheckInRangeConfig;
import com.vavr.checkincms.domain.CheckInRangeConfig.CheckInRange;
import com.vavr.checkincms.domain.bo.CheckinBO;
import com.vavr.checkincms.domain.data.CheckinRecordsDO;
import com.vavr.checkincms.domain.data.MeetingParticipantDO;
import com.vavr.checkincms.domain.dto.CheckinUserDTO;
import com.vavr.checkincms.domain.dto.MeetingConfigDTO;
import com.vavr.checkincms.mapper.CheckinRecordsMapper;
import com.vavr.checkincms.mapper.MeetingParticipantMapper;
import com.vavr.checkincms.util.CheckinRequestUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.stereotype.Service;

/**
 * CheckinServiceImpl
 *
 * @since 2024.08
 */
@Slf4j
@Service
public class CheckinServiceImpl implements CheckinService {

    /**
     * checkinRecordsMapper
     */
    private final CheckinRecordsMapper checkinRecordsMapper;

    /**
     * 签到时间相关配置
     */
    private final CheckinProperties checkinProperties;

    /**
     * 会议信息
     */
    private final MeetingInfoService meetingInfoService;

    /**
     * meetingParticipantMapper
     */
    private final MeetingParticipantMapper meetingParticipantMapper;

    public CheckinServiceImpl(CheckinRecordsMapper checkinRecordsMapper, CheckinProperties checkinProperties,
            MeetingInfoService meetingInfoService,MeetingParticipantMapper meetingParticipantMapper) {
        this.checkinRecordsMapper = checkinRecordsMapper;
        this.checkinProperties = checkinProperties;
        this.meetingInfoService = meetingInfoService;
        this.meetingParticipantMapper = meetingParticipantMapper;
    }

    @Override
    public void checkin(CheckinBO bo, HttpServletRequest request, HttpServletResponse response) {
        if(Strings.isEmpty(bo.getMeetingId())) {
            throw new BaseException(ResultEnum.MEETING_NOT_EXIST);
        }
        // 获取会议配置相关信息
        List<MeetingConfigDTO> meetingConfigs = meetingInfoService.getMeetingConfig(String.valueOf(bo.getMeetingId()));
        if(Objects.isNull(meetingConfigs) || meetingConfigs.isEmpty()) {
            throw new BaseException(ResultEnum.MEETING_NOT_EXIST);
        }
        // 记录/更新 与会人员信息
        CheckinUserDTO userInfo = checkinUser(bo, request, response, meetingConfigs);
        if (userInfo != null) {
            // 成功签到后,进一步确认是否需要跳转
            try {
                if(meetingConfigs.stream().anyMatch(config -> CheckinConstants.JUMP_AI.equals(config.getConfigId())
                        && CheckinConstants.IS_RIGHT.equals(config.getConfigValue()))) {
                   // 获取会议配置的分组问答信息
                   String groupId = meetingConfigs.stream()
                           .filter(config -> CheckinConstants.GROUP_ID.equals(config.getConfigId()))
                           .findFirst().map(MeetingConfigDTO::getConfigValue).orElse(null);
                   // 获取时间戳和签名
                    toRedirectAi(userInfo.getTel(), groupId, response);
                    return;
                }
            }
            catch (IOException e) {
                log.error(e.toString());
                throw new BaseException(ResultEnum.LOGIN_CHAT_AI_ERROR);
            }
            return;
        }
        throw new BaseException(ResultEnum.NOT_IN_CHECK_IN_RANGE);
    }

    @Override
    public String toChat(CheckinBO bo, HttpServletRequest request, HttpServletResponse response) throws IOException {
        if(Strings.isEmpty(bo.getMeetingId())) {
            throw new BaseException(ResultEnum.MEETING_NOT_EXIST);
        }
        // 通过session_id获取用户信息
        String tel = null;
        // 如果存在cookie信息,根据会议查询用户信息,若存在,返回查询结果信息
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            Optional<MeetingParticipantDO> participantDOOptional = Arrays.stream(cookies)
                    .filter(v -> CommonConstants.CHECKIN_COOKIE_ID.equals(v.getName()))
                    .findAny()
                    .map(v -> meetingParticipantMapper.selectById(v.getValue()));
            if (participantDOOptional.isPresent() && bo.getMeetingId()
                    .equals(participantDOOptional.get().getMeetingId()) && Strings.isNotBlank(
                    participantDOOptional.get().getTel())) {
                // 若果存在同session_id的同会议数据且手机号不为空,返回该数据
                // groupId = participantDOOptional.get().getGroupId();
                tel = participantDOOptional.get().getTel();
            }
        }
        String  groupId = meetingInfoService.getMeetingConfig(String.valueOf(bo.getMeetingId())).stream()
                .filter(config -> CheckinConstants.GROUP_ID.equals(config.getConfigId()))
                .findFirst().map(MeetingConfigDTO::getConfigValue).orElse(null);
        if(Strings.isEmpty(groupId)) {
            throw new BaseException(ResultEnum.MEETING_NOT_EXIST);
        }
        String timestamp = String.valueOf(System.currentTimeMillis());
        String signature = HmacSHAEncoderUtils.generateSignature(
                checkinProperties.getChatAppId() + tel + timestamp, checkinProperties.getChatAppSecret());
        // 组装chat-ai单点地址
      return  checkinProperties.getChatUrl() + "?groupId=" + groupId + "&appId=" + checkinProperties.getChatAppId()
                + "&tel=" + tel + "&signature=" + signature + "&timestamp=" + timestamp;
    }

    private void toRedirectAi(String tel, String groupId, HttpServletResponse response)
            throws IOException {
        String timestamp = String.valueOf(System.currentTimeMillis());
        String signature = HmacSHAEncoderUtils.generateSignature(
                checkinProperties.getChatAppId() + tel + timestamp, checkinProperties.getChatAppSecret());
        // 组装chat-ai单点地址
        String url = checkinProperties.getChatUrl() + "?groupId=" + groupId + "&appId=" + checkinProperties.getChatAppId()
                + "&tel=" + tel + "&signature=" + signature + "&timestamp=" + timestamp;
        response.setHeader("Location",url);
        response.setStatus(HttpServletResponse.SC_FOUND);
    }

    private CheckinUserDTO checkinUser(CheckinBO bo, HttpServletRequest request,
            HttpServletResponse response, List<MeetingConfigDTO> meetingConfigs) {
        // 获取签到时间配置
        CheckInRangeConfig rangeConfig = getCheckInRangeConfig(meetingConfigs);
        // 如果在签到时间内
        if (isWithinAllowedTimes(rangeConfig)) {
            // 获取用户签到记录
            QueryWrapper<CheckinRecordsDO> queryWrapper = new QueryWrapper<>();
            String telNum = bo.getTel();
            queryWrapper.eq("meeting_id", bo.getMeetingId());
            queryWrapper.eq("tel", telNum);
            queryWrapper.ge("checkin_time", getPreviousDate());
            List<CheckinRecordsDO> records = checkinRecordsMapper.selectList(queryWrapper);
            log.info("用户[{}]签到条数:{}", telNum, records.size());

            // 判断用户是否已存在符合签到时间要求的记录
            Optional<CheckinRecordsDO> checkinRecordsDO = hasAlreadyCheckInInTimeRange(records, rangeConfig);

            CheckinRecordsDO recordsDO;
            if (checkinRecordsDO.isPresent()) {
                recordsDO = checkinRecordsDO.get();
                log.info("用户[{}]已签到,checkId为:{}", telNum, recordsDO.getCheckId());
            }
            else {
                // 搜索本地该会议该用户是否已经存在,不存在则新增
                QueryWrapper<MeetingParticipantDO> queryMeetingUser = new QueryWrapper<>();
                queryMeetingUser.eq("meeting_id", bo.getMeetingId());
                queryMeetingUser.eq("tel", telNum);
                MeetingParticipantDO participantDO = meetingParticipantMapper.selectOne(queryMeetingUser);
                String userId;
                if(participantDO != null) {
                    userId = participantDO.getUserId();
                } else {
                    userId = IdUtil.fastSimpleUUID();
                }

                recordsDO = CheckinRecordsDO.builder()
                        .tel(telNum)
                        .name(bo.getName())
                        .userId(userId)
                        .organization(bo.getOrganization())
                        .checkinTime(new Date())
                        .meetingId(bo.getMeetingId())
                        .build();
                checkinRecordsMapper.insertCheckinRecord(recordsDO);

               if(participantDO == null) {
                   // 同时生成会议用户信息
                   MeetingParticipantDO mp = MeetingParticipantDO.builder()
                           .meetingId(bo.getMeetingId())
                           .userId(recordsDO.getUserId())
                           .name(bo.getName())
                           .tel(telNum)
                           .organization(bo.getOrganization())
                           .build();
                   meetingParticipantMapper.insert(mp);
               } else {
                   UpdateWrapper<MeetingParticipantDO> upMp = new UpdateWrapper<>();
                   upMp.eq("user_id", participantDO.getUserId()).set("name",bo.getName())
                           .set("organization",bo.getOrganization());
                   meetingParticipantMapper.update(upMp);
               }
            }
            CheckinUserDTO checkinUser = new CheckinUserDTO();
            checkinUser.setTel(telNum)
                    .setUserId(String.valueOf(recordsDO.getUserId()))
                    .setCheckinTime(recordsDO.getCheckinTime())
                    .setName(recordsDO.getName())
                    .setCheckId(String.valueOf(recordsDO.getCheckId()));
            // 将签到用户信息保存到会话中
            CheckinRequestUtils.setUserToRequestAttributes(checkinUser);
            response.addCookie(createCookie(checkinUser, request, rangeConfig));
            return checkinUser;
        }
        return null;
    }

    private CheckInRangeConfig getCheckInRangeConfig(List<MeetingConfigDTO> meetingConfigs) {
        try {
            String rangeParam = meetingConfigs.stream()
                    .filter(config -> CheckinConstants.CHECK_IN_RANGES.equals(config.getConfigId()))
                    .findFirst()
                    .map(MeetingConfigDTO::getConfigValue)
                    .orElse(null);
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.registerModule(new JavaTimeModule());
            return objectMapper.readValue(rangeParam, CheckInRangeConfig.class);
        }
        catch (JsonProcessingException e) {
            log.error("签到时间范围参数配置格式不正确，配置内容为:{}",meetingConfigs);
        }
        return new CheckInRangeConfig();
    }

    /**
     * 创建Cookie信息
     *
     * @param user {@link CheckinUserDTO}
     * @param request {@link HttpServletRequest}
     * @param checkInRangeConfig {@link CheckInRangeConfig}
     * @return 自定义Cookie
     */
    public Cookie createCookie(CheckinUserDTO user, HttpServletRequest request,
            CheckInRangeConfig checkInRangeConfig) {
        Cookie cookie = new Cookie(CommonConstants.CHECKIN_COOKIE_ID, user.getUserId());
        cookie.setPath(CommonConstants.CHECKIN_COOKIE_PATH);
        cookie.setHttpOnly(true);

        String header = request.getHeader(checkinProperties.getXForwardedProto());
        if (CharSequenceUtil.isNotBlank(header) && header.equalsIgnoreCase("https")) {
            cookie.setSecure(true);
        }
        Optional<CheckInRange> currentTimeRange = getCurrentTimeRange(checkInRangeConfig);
        if (currentTimeRange.isPresent()) {
            LocalDateTime localDateTime = LocalDateTime.now();
            long secondsLeft = Duration.between(localDateTime, currentTimeRange.get().getEndTime()).getSeconds();
            if (secondsLeft > 0) {
                cookie.setMaxAge((int) secondsLeft);
            }
        }
        return cookie;
    }

    /**
     * 是否在签到范围内
     *
     * @param checkInRangeConfig {@link CheckInRangeConfig}
     * @return true or false
     */
    private boolean isWithinAllowedTimes(CheckInRangeConfig checkInRangeConfig) {
        if (isBlankCheckInRange(checkInRangeConfig)) {
            return true;
        }
        return getCurrentTimeRange(checkInRangeConfig).isPresent();
    }

    /**
     * 获取当前时间所在时间返回区间
     *
     * @param checkInRangeConfig {@link CheckInRangeConfig}
     * @return {@link CheckInRange}
     */
    private Optional<CheckInRange> getCurrentTimeRange(CheckInRangeConfig checkInRangeConfig) {
        if (isBlankCheckInRange(checkInRangeConfig)) {
            return Optional.empty();
        }
        LocalDateTime localDateTime = LocalDateTime.now();
        return checkInRangeConfig.getCheckInRanges()
                .stream()
                .filter(v -> !localDateTime.isBefore(v.getStartTime()) && !localDateTime.isAfter(v.getEndTime()))
                .findAny();
    }

    private static boolean isBlankCheckInRange(CheckInRangeConfig checkInRangeConfig) {
        return checkInRangeConfig == null || checkInRangeConfig.getCheckInRanges() == null
                || checkInRangeConfig.getCheckInRanges().isEmpty();
    }

    /**
     * 在配置的时间范围内是否已存在签到记录，如果存在在返回第一条
     *
     * @param records 当前手机号签到集合
     * @param checkInRangeConfig {@link CheckInRangeConfig}
     * @return 返回一条签到记录
     */
    private Optional<CheckinRecordsDO> hasAlreadyCheckInInTimeRange(List<CheckinRecordsDO> records,
            CheckInRangeConfig checkInRangeConfig) {
        if (records == null || records.isEmpty()) {
            return Optional.empty();
        }
        if (checkInRangeConfig.getCheckInRanges() == null || checkInRangeConfig.getCheckInRanges().isEmpty()) {
            return Optional.empty();
        }
        return records.stream().filter(v -> {
            LocalDateTime checkinDateTime = v.getCheckinTime()
                    .toInstant()
                    .atZone(ZoneId.systemDefault())
                    .toLocalDateTime();
            return isWithinTimeRange(checkinDateTime, checkInRangeConfig);
        }).max(Comparator.comparing(CheckinRecordsDO::getCheckinTime));
    }

    /**
     * 数据库签到的时间是否在配置的时间范围内
     *
     * @param recordDateTime 数据库记录的签到时间
     * @param checkInRangeConfig {@link CheckInRangeConfig}
     * @return true or false
     */
    private boolean isWithinTimeRange(LocalDateTime recordDateTime, CheckInRangeConfig checkInRangeConfig) {
        return checkInRangeConfig.getCheckInRanges()
                .stream()
                .anyMatch(v -> !recordDateTime.isBefore(v.getStartTime()) && !recordDateTime.isAfter(v.getEndTime()));
    }

    /**
     * 获取前n天日期
     *
     * @return 前n天日期
     * @see CheckinProperties
     */
    private Date getPreviousDate() {
        Date currentDate = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(currentDate);
        calendar.add(Calendar.DAY_OF_YEAR, checkinProperties.getQueryCycle());
        return calendar.getTime();
    }
}
